package apps.MyJMRI;
import wiiremotej.*;
import wiiremotej.event.*;

import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import jmri.ThrottleManager;
import jmri.ThrottleListener;
import jmri.DccThrottle;
import jmri.jmrit.XmlFile;
import jmri.jmrit.roster.Roster;
import jmri.jmrit.roster.RosterEntry;

import net.iharder.dnd.*;
import lionel.input.*;

public class WiiControl extends WiiRemoteAdapter implements ThrottleListener, ButtonEventListener, AxisEventListener  {
	static org.apache.log4j.Category log = org.apache.log4j.Category.getInstance(WiiControl.class.getName());
	
	static float cruiseSpeed = 0.6f ;
	static float slowSpeed = 0.2f ;
	static String basePath = XmlFile.prefsDir()+"Images/";
	static String endPath = "/image";
	static String extPath=".jpg";

	private RemoteFrame frame;

	private WiiRemote remote;
	private JInputDevice m_pad;
	private boolean connected = false;
	
	private List<RosterEntry> rosters;
	private List<RosterEntry> runningRosters;
	private ListIterator<RosterEntry> idCur;
	private ListIterator<RosterEntry> runningRostersCur;

	private jmri.jmrit.roster.RosterEntry roster;
	private ThrottleManager tm ;
	private DccThrottle throttle;    
	private Image image;
	private myWorker worker = null;
	private int ONECount;
	private int TWOCount;
	
	private Timer throttleTimer;

	public WiiControl(RemoteFrame frame)
	{
		this.remote = null;
		this.m_pad = null;
		this.image = null;   
		this.connected = false;
		this.frame = frame;
		rosters = (List<RosterEntry>) Roster.instance().matchingList(null, null, null, null, null, null, null) ;
		if ((rosters==null) || (rosters.isEmpty()))
		{
			log.warn("Roster list is empty - Adding default rosters on DCC addresses 0 and 3");			
			RosterEntry entry0 = new RosterEntry();
			RosterEntry entry3 = new RosterEntry();
			entry0.setDccAddress("0");
			entry0.setModel("DC control");
			entry3.setDccAddress("3");
			entry3.setModel("Default DCC control");
			Roster.instance().addEntry(entry0 );
			Roster.instance().addEntry(entry3 );
			rosters = (List<RosterEntry>) Roster.instance().matchingList(null, null, null, null, null, null, null) ;
		}
		runningRosters = new LinkedList<RosterEntry>();		
		tm =jmri.InstanceManager.throttleManagerInstance() ;
		idCur = rosters.listIterator();
		roster = idCur.next();
		
		new FileDrop(frame, new FileDrop.Listener()
		    { public void filesDropped(File[] files) {

				String fullPath = files[0].getPath();
				log.debug("File dropped "+ fullPath);
				
				ImageIcon icon = new ImageIcon(fullPath);
				image = icon.getImage();		
				if (image.getWidth(null) == -1 )
				{ log.warn("Bad image file: "+fullPath+" Keeping previous one."); return; }
				
				changecurentRosterImage(files[0]);
		    } } );
		
		throttleTimer = new Timer(1000, new ActionListener() {
		      public void actionPerformed(ActionEvent e) {
				synchronized(roster)
					{					
						if (throttle == null)
						{
							log.warn("Missing throttle for: "+roster.getId()+" "+roster.getDccLocoAddress());
							ImageIcon icon = new ImageIcon(getClass().getResource("NoThrottle.jpg"));
							image = icon.getImage();
							frameRepaint();
						}
					}
		      } } );
	}

	protected void frameRepaint() {
		frame.repaint();
	}

	public void setRemote(WiiRemote remote)
	{
		try{
			this.remote = remote;
			remote.addWiiRemoteListener(this);
			remote.requestStatus();
			remote.setLEDIlluminated(0, true);
			remote.setAccelerometerEnabled(false);
			remote.setIRSensorEnabled(false, WRIREvent.BASIC);
			remote.vibrateFor(250);
			remote.setSpeakerEnabled(false);			
			} catch (Exception e) {
				log.warn("Got IO Exception: "+e.toString() );
			}
		connected = true;
		changeThrottle();
	}
	
	public void setPad(JInputDevice pad)
	{
		m_pad = pad;
		m_pad.addButtonEventListener(this);
		m_pad.addAxisEventListener(this);
		connected = true;
		changeThrottle();
		log.debug("Connected to JInputDevice"+m_pad.getName()+m_pad);
	}

	public void statusReported(WRStatusEvent evt)
	{
		log.warn("Battery level: " + (double)evt.getBatteryLevel()/2+ "%");
		log.warn("Continuous: " + evt.isContinuousEnabled());
		log.warn("Remote continuous: " + remote.isContinuousEnabled());
	}

	public void disconnected()
	{
		log.error("Remote disconnected...");
		remote.removeWiiRemoteListener(this);
		this.remote = null;
		loc_accStop();
		this.connected = false;
	}
	
	public boolean isConnected() 
	{ return connected ; }

	private void loadImage()
	{
		String imagePath = basePath + roster.getModel() + "-" + roster.getRoadName() + "-" + roster.getRoadNumber() + endPath;
		
		int i=0;
		File file = new File(imagePath +i + extPath);
		while ( file.exists() )
		{ i++ ; file = new File(imagePath + i + extPath); }
		i--;
		
		ImageIcon icon = new ImageIcon(imagePath + i + extPath);
		image = icon.getImage();			
		if (image.getWidth(null) == -1 )
			log.error("No such image: "+imagePath + i + extPath);	
		frame.repaint();
	}

	public Image getImage()
	{   
		if ((image==null) || (image.getWidth(null) == -1)) 
		{
			if ((remote != null) || (m_pad!=null))
			{
				ImageIcon icon = new ImageIcon(getClass().getResource("NoLocImage.jpg"));
				image = icon.getImage();
			}
			else
			{
				ImageIcon icon = new ImageIcon(getClass().getResource("NoControl.jpg"));
				image = icon.getImage();
			}
		}
		return image;
	}

	public String getIDString()
	{
		if ((remote == null) && (m_pad==null))
			return("No Wiimote, press 1+2; no pad");
		if ((roster != null) && (throttle != null))
			return(roster.getModel() + " - " + roster.getRoadName() + " " + roster.getRoadNumber() + " / " + throttle.toString() );
		if (roster!=null)
			return(roster.getModel() + " - " + roster.getRoadName() + " " + roster.getRoadNumber() + " "+roster.getDccLocoAddress()+" throttle not available");
		return("MyJRMI");
	}

	public void notifyThrottleFound(DccThrottle t)
	{  	
		synchronized(roster)
		{
			throttle = t;
			ONECount = 0; TWOCount = 0;  
			loadImage();
			throttleTimer.stop();
		}
		log.debug("Throttle for loc "+roster.getModel() + " - " + roster.getRoadName() + " " + roster.getRoadNumber());
	}

	private void changeThrottle()
	{   // Synchronized through lock on roster
		// Maybe it's not such a good idea to acquire a new throttle at each new entry
		if ( tm.requestThrottle(roster.getDccLocoAddress().getNumber() , this) == false)
			log.error(roster.getId()+"/ request throttle for "+roster.getDccLocoAddress().getNumber()+" failed");
		throttleTimer.start();
		try {
			Thread.sleep(50);
		} catch (InterruptedException e) {
			log.warn("Interruped after call for throttle"+roster.getDccLocoAddress().getNumber());
		}
	}

	private void releaseThrottle()
	{  // Synchronized through lock on roster
			loc_accStop();
			if (throttle != null)
			{        	
				throttle.dispatch();
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				throttle = null;
				log.debug("Dispatched throttle "+roster.getDccLocoAddress().getNumber());
			}
			else
			{
				tm.cancelThrottleRequest(roster.getDccLocoAddress().getNumber() , this);
				try {Thread.sleep(50);} catch (InterruptedException e) {}
				log.warn("Callback canceled "+roster.getDccLocoAddress().getNumber());
			}
	}

	private void loc_accStop()
	{
		if (worker != null)
		{	
			worker.interrupt();
		}
		worker=null;   
	} 

	private void loc_eStop()
	{
		loc_accStop();
		throttle.setSpeedSetting( -0.5f ); 
		ONECount=0; TWOCount=0;
		if (remote!=null)
			try {
				remote.vibrateFor(500);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				log.error("Exception while vibrating: "+ e.toString());
				e.printStackTrace();	
			}
		if (m_pad!=null)
			log.debug("Pad has got "+m_pad.getNumRumblers()+" rumblers");
	}
	
	private void loc_slow()	
	{
		loc_accStop();
		TWOCount++; ONECount = 0;
		switch (TWOCount)
		{
			case 1: throttle.setSpeedSetting(slowSpeed); break;
			case 2: throttle.setSpeedSetting(0); TWOCount = 0; break;	        		
		}
	}  
	
	private void loc_cruise()
	{
		loc_accStop();
		ONECount++; TWOCount = 0;
		switch (ONECount)
		{
			case 1: throttle.setSpeedSetting(cruiseSpeed); break;
			case 2: throttle.setSpeedSetting(1); ONECount=0; break;
		}
	}

	private void loc_acc(boolean yn)
	{
		loc_accStop();
		ONECount = 0; TWOCount = 0;
		worker = new myWorker(yn?1:-1, throttle);        	
	}
	
	private void loc_setDir(boolean dir)
	{
		ONECount = 0; TWOCount = 0;
		loc_accStop();
		throttle.setIsForward( dir );
	}
	
	private void loc_switchLights()
	{
		ONECount = 0; TWOCount = 0;
		//log.debug("Home - lights");
		throttle.setF0(! throttle.getF0() ) ;
	}
	
	private void addToRunningRosters()
	{	
		// mess with the list and the cursor
		if (runningRosters.size()==0)
		{
			runningRosters.add(roster);
			runningRostersCur = runningRosters.listIterator();
		}
		else
			if (!runningRosters.contains(roster))
				runningRostersCur.add(roster);
	}

	private void removeFromRunningRosters()
	{
		if ( (runningRosters.size()>0) && (runningRosters.contains(roster)) )
		{
			runningRosters.remove(roster);
			if (runningRosters.size()>0)
				runningRostersCur = runningRosters.listIterator();
		}
	}
	
	private void moveToNextRoster()
	{
		synchronized(roster) {
			if (rosters.size()>1)
			{        	
				releaseThrottle();
				jmri.jmrit.roster.RosterEntry rosterPrev = roster ;
				while ( roster == rosterPrev )
				{
					if (! idCur.hasNext())
						idCur = rosters.listIterator();
					roster = idCur.next() ; 
				}
				//log.debug(roster.getId()+" "+roster.getDccLocoAddress());        	
				changeThrottle();
			}
		}
	}
	
	private void moveToPreviousRoster()
	{
		synchronized(roster) {
			if (rosters.size()>1)
			{
				releaseThrottle();
				jmri.jmrit.roster.RosterEntry rosterPrev = roster ;
				while ( roster == rosterPrev )
				{   
					if (! idCur.hasPrevious())
						idCur = rosters.listIterator(rosters.size());
					roster = idCur.previous() ;    			        		
				}
				//log.debug(roster.getId()+" "+roster.getDccLocoAddress());        	
				changeThrottle();
			}
		}
	}
	
	private void moveToPreviousRunningRoster()
	{
		synchronized(roster) {
			if ( runningRosters.size()>1 )
			{
				releaseThrottle();     		
				jmri.jmrit.roster.RosterEntry rosterPrev = roster ;
				while ( roster == rosterPrev )
				{  
					if (! runningRostersCur.hasPrevious())
						runningRostersCur = runningRosters.listIterator(runningRosters.size());
					roster = runningRostersCur.previous() ;    			        		
				}
				changeThrottle();	        	
			}
			else if ( runningRosters.size()== 1 )
			{
				jmri.jmrit.roster.RosterEntry rosterPrev = runningRosters.get(0) ;
				if (roster != rosterPrev)
				{
					releaseThrottle(); 
					roster = rosterPrev;
					changeThrottle();
				}     	
			}
		}
	}
	
	private void moveToNextRunningRoster()
	{
		synchronized(roster) {       	
			if ( runningRosters.size()>1 )
			{
				releaseThrottle();     		
				jmri.jmrit.roster.RosterEntry rosterPrev = roster ;
				while ( roster == rosterPrev )
				{  
					if (! runningRostersCur.hasNext())
						runningRostersCur = runningRosters.listIterator();
					roster = runningRostersCur.next() ;    			        		
				} 
				changeThrottle();	        	
			}
			else if ( runningRosters.size()== 1 )
			{
				jmri.jmrit.roster.RosterEntry rosterPrev = runningRosters.get(0) ;
				if (roster != rosterPrev)
				{
					releaseThrottle(); 
					roster = rosterPrev;
					changeThrottle();
				}     	
			}
		}
	}
	
	public void buttonInputReceived(WRButtonEvent evt)
	{
		if (throttle != null)
		{
			if ( (evt.isPressed(WRButtonEvent.ONE) && evt.isPressed(WRButtonEvent.TWO)) )  // E-stop
				loc_eStop();
			else
			{
				if (evt.wasPressed(WRButtonEvent.TWO))  // slow - idle
					loc_slow();
				if (evt.wasPressed(WRButtonEvent.ONE)) 	// cruise - max
					loc_cruise();
			}
			if (evt.isPressed(WRButtonEvent.A)) 	// Speed -- loop start
				loc_acc(false);
			if (evt.isPressed(WRButtonEvent.B)) 	// Speed ++ loop start
				loc_acc(true);
			if ( (evt.wasReleased(WRButtonEvent.A)) || (evt.wasReleased(WRButtonEvent.B)) )	//Speed loop stop
				loc_accStop();
			if (evt.wasPressed(WRButtonEvent.PLUS))		// Direction
				loc_setDir(true);
			if (evt.wasPressed(WRButtonEvent.MINUS))
				loc_setDir(false);      
			if (evt.wasPressed(WRButtonEvent.HOME))     // Lights
				loc_switchLights();

			// Is it a running locomotive?
			if (throttle.getSpeedSetting()>0)
				addToRunningRosters();
			else
				removeFromRunningRosters();
		}

		if (evt.wasPressed(WRButtonEvent.RIGHT)) 	// next roster
			moveToNextRoster();
		if (evt.wasPressed(WRButtonEvent.LEFT)) 	// prev roster
			moveToPreviousRoster();
		if (evt.wasPressed(WRButtonEvent.UP))	// previous running roster
			moveToPreviousRunningRoster();
		if (evt.wasPressed(WRButtonEvent.DOWN)) 	// next running roster
			moveToNextRunningRoster();
	}
	
	private void changecurentRosterImage(File file)
	{
		if (roster == null)
		{ log.warn("No roster at the moment"); return; }
		
		synchronized(roster)
		{	
			String imagePath = basePath + roster.getModel() + "-" + roster.getRoadName() + "-" + roster.getRoadNumber() + endPath ;		
			int i=0;
			File copy = new File(imagePath +i + extPath);
			while ( copy.exists() ) 
			{ i++ ; copy = new File(imagePath +i + extPath); }
	
			if (i==0)
				copy.getParentFile().mkdirs();			
			
			if ( copy.getParentFile().compareTo( file.getParentFile() ) != 0 )
			{
				try
				{ copyFile(file, copy); }
			    catch (Exception e) 
			    { log.warn("Exception while copying file: "+e.toString()); }
			}
			else
			{
				File prev = file;
				file.renameTo(copy);
				try
				{ prev.createNewFile(); }
			    catch (Exception e) 
			    { log.warn("Exception while creating empty file: "+e.toString()); }
			}
	
			log.debug("File copied to "+ imagePath +i + extPath);
			loadImage();
		}
	}
	
	private static void copyFile(File in, File out) throws Exception {
		    FileInputStream fis  = new FileInputStream(in);
		    FileOutputStream fos = new FileOutputStream(out);
		    try {
		        byte[] buf = new byte[1024];
		        int i = 0;
		        while ((i = fis.read(buf)) != -1) {
		            fos.write(buf, 0, i);
		        }
		    } 
		    catch (Exception e) {
		        throw e;
		    }
		    finally {
		        if (fis != null) fis.close();
		        if (fos != null) fos.close();
		    }
		  }

	private boolean is2Pressed = false;
	private boolean ignore2release = false;
	private boolean is3Pressed = false;
	private boolean ignore3release = false;
	
	public void buttonPressed(ButtonEvent evt) {
		// TODO Auto-generated method stub
		log.debug("Button pressed: "+evt.getID() );
		
		if (!is2Pressed) is2Pressed = (evt.getID() == JInputComponent.ID.BUTTON__2);
		if (!is3Pressed) is3Pressed = (evt.getID() == JInputComponent.ID.BUTTON__3);
		if (is2Pressed && is3Pressed) // E-stop
		{
			loc_eStop();
			ignore2release = true; ignore3release = true;
			is2Pressed = false; is3Pressed = false;
		}
		if (evt.getID() == JInputComponent.ID.BUTTON__0) 	// Speed -- loop start
			loc_acc(true);
		if (evt.getID() == JInputComponent.ID.BUTTON__1) 	// Speed ++ loop start
			loc_acc(false);
	}

	public void buttonReleased(ButtonEvent evt) {
		// TODO Auto-generated method stub	
		log.debug("Button released: "+evt.getID() );
		loc_accStop();
		
		if (is2Pressed) is2Pressed = !(evt.getID() == JInputComponent.ID.BUTTON__2);
		if (is3Pressed) is3Pressed = !(evt.getID() == JInputComponent.ID.BUTTON__3);
		if ((!ignore2release) && (evt.getID() == JInputComponent.ID.BUTTON__2))  // slow - idle
			loc_slow();
		else
		  if ((ignore2release) && (evt.getID() == JInputComponent.ID.BUTTON__2))
			ignore2release = false;
		
		if ((!ignore3release) && (evt.getID() == JInputComponent.ID.BUTTON__3)) 	// cruise - max
			loc_cruise();
		else 
		  if ((ignore3release) && (evt.getID() == JInputComponent.ID.BUTTON__3))
			ignore3release = false;
		
		if (evt.getID() == JInputComponent.ID.BUTTON__4)	// Direction
			loc_setDir(true);
		if (evt.getID() == JInputComponent.ID.BUTTON__5)
			loc_setDir(false);      
		if (evt.getID() == JInputComponent.ID.BUTTON__6)    // Lights
			loc_switchLights();
	
	}

	long timePreviousAxis = 0;
	JInputComponent.POV kindPreviousAxis;
	
	public void axisMoved(AxisEvent evt) {
		log.debug("Axis moved: "+evt.getID() +" / "+ evt.getValue() +" / "+ evt.getPOVValue() );
		// Is it a running locomotive?
		if ((throttle!=null) && (throttle.getSpeedSetting()>0))
			addToRunningRosters();
		else
			removeFromRunningRosters();	
		if ( evt.getID() == JInputComponent.ID.AXIS_POV )
		{
			long timeNow = System.currentTimeMillis();
			if ( (kindPreviousAxis!=evt.getPOVValue()) || (timeNow - timePreviousAxis>50) )
			{
				if ( evt.getPOVValue() == JInputComponent.POV.RIGHT )
					moveToNextRoster();
				if ( evt.getPOVValue() == JInputComponent.POV.LEFT ) 	
					moveToPreviousRoster();
				if ( evt.getPOVValue() == JInputComponent.POV.UP )	// previous running roster
					moveToPreviousRunningRoster();
				if ( evt.getPOVValue() == JInputComponent.POV.DOWN ) 	// next running roster
					moveToNextRunningRoster();
				timePreviousAxis = timeNow;
				kindPreviousAxis = evt.getPOVValue();
			}
		}
	}
}
